﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Graphics;

namespace WorldOnFire
{
    public class CSkirmishState : ABCState
    {
        #region Static Pointers

        private static CPlatformState s_PlatformState;
        private static CObjectManager s_ObjectManager;

        public static void SetPlatformState(CPlatformState _PlatformState)
        {
            s_PlatformState = _PlatformState;
            s_ObjectManager = _PlatformState.GetObjectManager();
        }

        #endregion Static Pointers

        #region Camera

        // Camera data
        private float m_fCameraHeight;
        private float m_fCameraRotation;

        // Constants
        const float CAMERA_SPEED = 250.0f;
        const float CAMERA_ROTATION_SPEED = (float)Math.PI / 1.5f;
        const float CAMERA_OFFSET_Z = 50.0f;
        const float CAMERA_MINIMUM_HEIGHT = -25.0f;
        const float CAMERA_MAXIMUM_HEIGHT = 1000.0f;

        #endregion Camera

        #region Grid

        // TODO: Make grid and reticle vertices not fucktarded
        private static VertexPositionTexture[] s_vGridVertices;

        private const float GRID_SIZE = 250.0f;
        private const int GRID_ROWS = 11;
        private const int GRID_COLUMNS = 11;

        private CRenderingManager.eTextureID m_eGridTextureID;

        private static VertexPositionTexture[] s_vReticleVertices = new VertexPositionTexture[6]
        {
            new VertexPositionTexture(new Vector3(-25.0f, 0.0f, -25.0f), new Vector2(0.0f, 0.0f)),  // TOP LEFT
            new VertexPositionTexture(new Vector3(25.0f, 0.0f, -25.0f), new Vector2(1.0f, 0.0f)),   // TOP RIGHT
            new VertexPositionTexture(new Vector3(-25.0f, 0.0f, 25.0f), new Vector2(0.0f, 1.0f)),   // BOTTOM LEFT
            new VertexPositionTexture(new Vector3(-25.0f, 0.0f, 25.0f), new Vector2(0.0f, 1.0f)),   // BOTTOM LEFT
            new VertexPositionTexture(new Vector3(25.0f, 0.0f, -25.0f), new Vector2(1.0f, 0.0f)),   // TOP RIGHT
            new VertexPositionTexture(new Vector3(25.0f, 0.0f, 25.0f), new Vector2(1.0f, 1.0f))     // BOTTOM RIGHT
        };

        private bool m_bGridActive;

        #endregion Grid

        #region Reticle

        private Vector3 m_vReticle;

        private CRenderingManager.eTextureID m_eReticleTextureID;

        private bool m_bReticleActive;

        private Queue<int> m_SatelliteIndexStack;
        private Queue<Vector3> m_DestinationStack;

        private int m_nSelectedIndex;

        // TODO: HACK HACK HACK
        private DynamicVertexBuffer m_TransformVertexBuffer;
        private Matrix[] m_mBoneTransforms;
        private Matrix[] m_mRenderingTransforms;

        #endregion Reticle

        #region Rendering Data

        protected int m_nTechniqueID;

        public int GetTechniqueID()
        { return m_nTechniqueID; }

        public void SetTechniqueID(int _nTechniqueID)
        { m_nTechniqueID = _nTechniqueID; }

        #endregion Rendering Data

        public override void Initialize()
        {
            // Set rendering manager's HUD pointer
            s_RenderingManager.SetHUDState(this);

            // Set up camera stuff
            m_fCameraHeight = 200.0f;

            // Initialize reticle
            //m_bReticleActive = true;
            m_vReticle = Vector3.Zero;
            m_nSelectedIndex = -1;
            m_DestinationStack = new Queue<Vector3>();
            m_SatelliteIndexStack = new Queue<int>();

            // Initialize grid
           // m_bGridActive = true;
            m_eGridTextureID = CRenderingManager.eTextureID.HUD_GRID;
            s_vGridVertices = new VertexPositionTexture[2 * (GRID_ROWS + GRID_COLUMNS)];

            float fGridOffsetX = 0.5f * GRID_COLUMNS * GRID_SIZE - 0.5f * GRID_SIZE;
            float fGridOffsetZ = 0.5f * GRID_ROWS * GRID_SIZE - 0.5f * GRID_SIZE;

            for (int iRow = 0; iRow < GRID_ROWS; ++iRow)
            {
                Vector3 vFrom = new Vector3(-fGridOffsetX, 0.0f, iRow * GRID_SIZE - fGridOffsetZ);
                Vector3 vTo = new Vector3(fGridOffsetX, 0.0f, iRow * GRID_SIZE - fGridOffsetZ);

                s_vGridVertices[iRow * 2]
                    = new VertexPositionTexture(vFrom, new Vector2(0.5f, 0.5f));
                s_vGridVertices[iRow * 2 + 1]
                    = new VertexPositionTexture(vTo, new Vector2(0.5f, 0.5f));
            }

            for (int iColumn = 0; iColumn < GRID_COLUMNS; ++iColumn)
            {
                Vector3 vFrom = new Vector3(iColumn * GRID_SIZE - fGridOffsetX, 0.0f, -fGridOffsetZ);
                Vector3 vTo = new Vector3(iColumn * GRID_SIZE - fGridOffsetX, 0.0f, fGridOffsetZ);

                s_vGridVertices[iColumn * 2 + GRID_ROWS * 2]
                    = new VertexPositionTexture(vFrom, new Vector2(0.0f, 0.0f));
                s_vGridVertices[iColumn * 2 + GRID_ROWS * 2 + 1]
                    = new VertexPositionTexture(vTo, new Vector2(0.0f, 0.0f));
            }

            // Retrieve the model from the manager
            Model ObjectModel = s_RenderingManager.GetModel(CRenderingManager.eModelID.RETICLE);

            // Copy the bone transforms for easy access
            m_mBoneTransforms = new Matrix[ObjectModel.Bones.Count];
            ObjectModel.CopyAbsoluteBoneTransformsTo(m_mBoneTransforms);

            //float fGridOffsetX = 0.5f * GRID_COLUMNS * GRID_SIZE;
            //float fGridOffsetZ = 0.5f * GRID_ROWS * GRID_SIZE;

            //for (int iRow = 0; iRow < GRID_ROWS; ++iRow)
            //{
            //    for (int iColumn = 0; iColumn < GRID_COLUMNS; ++iColumn)
            //    {
            //        Vector3 vTopLeft = new Vector3(iColumn * GRID_SIZE - fGridOffsetX, 0.0f, iRow * GRID_SIZE - fGridOffsetZ);
            //        Vector3 vTopRight = new Vector3((iColumn + 1) * GRID_SIZE - fGridOffsetX, 0.0f, iRow * GRID_SIZE - fGridOffsetZ);
            //        Vector3 vBottomLeft = new Vector3(iColumn * GRID_SIZE - fGridOffsetX, 0.0f, (iRow + 1) * GRID_SIZE - fGridOffsetZ);
            //        Vector3 vBottomRight = new Vector3((iColumn + 1) * GRID_SIZE - fGridOffsetX, 0.0f, (iRow + 1) * GRID_SIZE - fGridOffsetZ);

            //        s_vGridVertices[iRow * GRID_COLUMNS * 6 + iColumn * 6 + 0] 
            //            = new VertexPositionTexture(vTopLeft, new Vector2(0.0f, 0.0f));      // TOP LEFT
            //        s_vGridVertices[iRow * GRID_COLUMNS * 6 + iColumn * 6 + 1] 
            //            = new VertexPositionTexture(vTopRight, new Vector2(1.0f, 0.0f));     // TOP RIGHT
            //        s_vGridVertices[iRow * GRID_COLUMNS * 6 + iColumn * 6 + 2] 
            //            = new VertexPositionTexture(vBottomLeft, new Vector2(0.0f, 1.0f));   // BOTTOM LEFT
            //        s_vGridVertices[iRow * GRID_COLUMNS * 6 + iColumn * 6 + 3] 
            //            = new VertexPositionTexture(vBottomLeft, new Vector2(0.0f, 1.0f));   // BOTTOM LEFT
            //        s_vGridVertices[iRow * GRID_COLUMNS * 6 + iColumn * 6 + 4] 
            //            = new VertexPositionTexture(vTopRight, new Vector2(1.0f, 0.0f));     // TOP RIGHT
            //        s_vGridVertices[iRow * GRID_COLUMNS * 6 + iColumn * 6 + 5] 
            //            = new VertexPositionTexture(vBottomRight, new Vector2(1.0f, 1.0f));  // BOTTOM RIGHT
            //    }
            //}
        }

        public override void Shutdown()
        {

        }

        public override void Input(float _fDeltaTime)
        {
            ////////////////////////////////////////////////////////////////////////////////////////
            /* BEGIN: Camera input ===============================================================*/

            // Get the camera transform
            Matrix mCameraTransform = s_RenderingManager.GetCamera().GetTransform();

            // Calculate the camera's speed
            float fCameraSpeed = CAMERA_SPEED * _fDeltaTime;
            float fCameraRotationSpeed = CAMERA_ROTATION_SPEED * _fDeltaTime;

            // Camera movement
            if (SInputManager.IsKeyDown(Keys.W) == true)
            {
                Vector3 vDirection = mCameraTransform.Forward;
                vDirection.Y = 0.0f;
                vDirection.Normalize();
                m_vReticle += fCameraSpeed * vDirection;
            }
            if (SInputManager.IsKeyDown(Keys.S) == true)
            {
                Vector3 vDirection = mCameraTransform.Backward;
                vDirection.Y = 0.0f;
                vDirection.Normalize();
                m_vReticle += fCameraSpeed * vDirection;
            }
            if (SInputManager.IsKeyDown(Keys.A) == true)
            {
                Vector3 vDirection = mCameraTransform.Left;
                m_vReticle += fCameraSpeed * vDirection;
            }
            if (SInputManager.IsKeyDown(Keys.D) == true)
            {
                Vector3 vDirection = mCameraTransform.Right;
                m_vReticle += fCameraSpeed * vDirection;
            }

            if (SInputManager.IsKeyDown(Keys.Down) == true)
            {
                m_fCameraHeight += fCameraSpeed;
                if (m_fCameraHeight > CAMERA_MAXIMUM_HEIGHT)
                {
                    m_fCameraHeight = CAMERA_MAXIMUM_HEIGHT;
                }
            }
            if (SInputManager.IsKeyDown(Keys.Up) == true)
            {
                m_fCameraHeight -= fCameraSpeed;
                if (m_fCameraHeight < CAMERA_MINIMUM_HEIGHT)
                {
                    m_fCameraHeight = CAMERA_MINIMUM_HEIGHT;
                }
            }

            if (SInputManager.IsKeyDown(Keys.Left) == true)
            {
                m_fCameraRotation += fCameraRotationSpeed;
            }
            if (SInputManager.IsKeyDown(Keys.Right) == true)
            {
                m_fCameraRotation -= fCameraRotationSpeed;
            }

            // Set the camera to look at 
            Vector3 vCameraOffset = m_fCameraHeight * new Vector3(0.0f, 1.0f, 0.5f);
            vCameraOffset.Z += CAMERA_OFFSET_Z;
            vCameraOffset = Vector3.Transform(vCameraOffset, Matrix.CreateRotationY(m_fCameraRotation));

            Vector3 vCameraPosition = m_vReticle + vCameraOffset;
            SEventSystem.SendEvent(new CEventCameraLookAt(m_vReticle, vCameraPosition));

            /* END: Camera input =================================================================*/
            ////////////////////////////////////////////////////////////////////////////////////////

            // Game speed
            if (SInputManager.IsKeyDown(Keys.Enter) == true)
            {
                s_PlatformState.SetUpdateSpeed(5.0f);
            }
            else
            {
                s_PlatformState.SetUpdateSpeed(1.0f);
            }

            // View toggle
            if (SInputManager.IsKeyPressed(Keys.Space) == true)
            {
                CRenderingManager.ePostProcessingEffect PPEffect = s_RenderingManager.GetPostProcessingEffect();
                PPEffect = (CRenderingManager.ePostProcessingEffect)((int)PPEffect + 1);
                if (PPEffect == CRenderingManager.ePostProcessingEffect.COUNT)
                {
                    PPEffect = CRenderingManager.ePostProcessingEffect.DEFAULT;
                }
                s_RenderingManager.SetPostProcessingEffect(PPEffect);
            }

            // Grid toggle
            if (SInputManager.IsKeyPressed(Keys.G) == true)
            {
                m_bGridActive = !m_bGridActive;
            }

            // Reticle toggle
            if (SInputManager.IsKeyPressed(Keys.R) == true)
            {
                m_bReticleActive = !m_bReticleActive;
            }

            // Celestial body selection
            if (SInputManager.IsKeyPressed(Keys.RightShift) == true)
            {
                CSatellite Satellite = s_ObjectManager.GetCelestialBody();

                m_nSelectedIndex = Satellite.GetFirstCollidingIndex(m_vReticle, 25.0f);
            }

            // Celestial body movement
            if (SInputManager.IsKeyPressed(Keys.RightControl) == true)
            {
                if (m_nSelectedIndex != -1)
                {
                    CSatellite Satellite = s_ObjectManager.GetCelestialBody();

                    if (SInputManager.IsKeyDown(Keys.LeftShift) == false)
                    {
                        m_SatelliteIndexStack.Clear();
                        m_DestinationStack.Clear();
                        Satellite.StopSatellite();
                    }

                    m_SatelliteIndexStack.Enqueue(m_nSelectedIndex);
                    m_DestinationStack.Enqueue(m_vReticle);
                    if (Satellite.IsSatelliteMoving() == false)
                    {
                        Satellite.MoveSatellite(m_nSelectedIndex, m_vReticle);
                    }
                }
            }
        }

        public override void Update(float _fDeltaTime)
        {
            // Move the satellite towards its destination
            if (m_SatelliteIndexStack.Count > 0)
            {
                CSatellite Satellite = s_ObjectManager.GetCelestialBody();

                if (Satellite.IsSatelliteMoving() == false)
                {
                    m_SatelliteIndexStack.Dequeue();
                    m_DestinationStack.Dequeue();
                    if(m_SatelliteIndexStack.Count > 0)
                    {
                        Satellite.MoveSatellite(m_SatelliteIndexStack.Peek(), m_DestinationStack.Peek());
                    }
                }
            }
        }

        public override void Render()
        {
            // TODO: HUD
            // Get the device
            GraphicsDevice Device = s_RenderingManager.GetGraphicsDevice();

            // Set technique
            Effect HUDEffect = s_RenderingManager.GetEffect(CRenderingManager.eEffectID.HUD);
            HUDEffect.CurrentTechnique = HUDEffect.Techniques[m_nTechniqueID];

            // Set device states
            Device.RasterizerState = RasterizerState.CullNone;
            if (m_nTechniqueID == (int)CRenderingManager.eHUDTechnique.DEFAULT)
            {
                Device.BlendState = BlendState.AlphaBlend;
            }
            else
            {
                Device.BlendState = BlendState.Opaque;
            }

            // If the grid is active
            if (m_bGridActive == true)
            {
                // Draw grid
                HUDEffect.Parameters["g_Texture"].SetValue(s_RenderingManager.GetTexture(m_eGridTextureID));
                HUDEffect.Parameters["g_mWorld"].SetValue(Matrix.Identity);

                // Loop through each pass
                foreach (EffectPass Pass in HUDEffect.CurrentTechnique.Passes)
                {
                    // Begin the pass
                    Pass.Apply();

                    // Draw magic things
                    Device.DrawUserPrimitives<VertexPositionTexture>(PrimitiveType.LineList, s_vGridVertices, 0, GRID_ROWS + GRID_COLUMNS);
                }
            }

            // If the reticle is active
            if (m_bReticleActive == true)
            {
                // Render reticle
                //HUDEffect.Parameters["g_Texture"].SetValue(s_RenderingManager.GetTexture(m_eReticleTextureID));

                //Matrix mReticleTransform = Matrix.CreateRotationY(m_fCameraRotation);
                //mReticleTransform.Translation = m_vReticle;
                //HUDEffect.Parameters["g_mWorld"].SetValue(mReticleTransform);

                //// Loop through each pass
                //foreach (EffectPass Pass in HUDEffect.CurrentTechnique.Passes)
                //{
                //    // Begin the pass
                //    Pass.Apply();

                //    // Draw magic things
                //    Device.DrawUserPrimitives<VertexPositionTexture>(PrimitiveType.TriangleList, s_vReticleVertices, 0, 2);
                //}

                // Draw destination reticles
                // Set render states
                Device.DepthStencilState = DepthStencilState.Default;
                Device.RasterizerState = RasterizerState.CullCounterClockwise;
                Device.BlendState = BlendState.Opaque;

                ////////////////////////////////////////////////////////////////////////////////////////
                /* BEGIN: Set up rendering transforms and buffers ====================================*/
                // Create the array of rendering transforms
                m_mRenderingTransforms = new Matrix[1 + m_DestinationStack.Count];

                // Copy the transforms
                Matrix mRotation = Matrix.CreateRotationY(m_fCameraRotation);
                m_mRenderingTransforms[0] = mRotation * Matrix.CreateTranslation(m_vReticle);
                for (int iRenderingIndex = 1; iRenderingIndex < 1 + m_DestinationStack.Count; ++iRenderingIndex)
                {
                    // Copy the transform into the rendering transform array
                    m_mRenderingTransforms[iRenderingIndex] =
                        mRotation * Matrix.CreateTranslation(m_DestinationStack.ElementAt(iRenderingIndex - 1));
                }

                // If the vertex buffer is uninitialized or too small
                if ((m_TransformVertexBuffer == null) ||
                    (m_TransformVertexBuffer.VertexCount < m_mRenderingTransforms.Length))
                {
                    // If the vertex buffer is initialized
                    if (m_TransformVertexBuffer != null)
                    {
                        // Delete it
                        m_TransformVertexBuffer.Dispose();
                    }

                    // Allocate a new vertex buffer
                    m_TransformVertexBuffer =
                        new DynamicVertexBuffer(Device, ABCObject.s_InstanceVertexDeclaration,
                            m_mRenderingTransforms.Length, BufferUsage.WriteOnly);
                }

                // Transfer the instance transforms to the buffer
                m_TransformVertexBuffer.SetData(m_mRenderingTransforms, 0,
                    m_mRenderingTransforms.Length, SetDataOptions.Discard);
                /* END: Set up rendering transforms and buffers ======================================*/
                ////////////////////////////////////////////////////////////////////////////////////////

                ////////////////////////////////////////////////////////////////////////////////////////
                /* BEGIN: Render stuff ===============================================================*/
                // Get the effect from the rendering manager
                Effect ObjectEffect = s_RenderingManager.GetEffect(CRenderingManager.eEffectID.OBJECT);
                if (m_nTechniqueID == (int)CRenderingManager.eHUDTechnique.DEPTH)
                {
                    ObjectEffect.CurrentTechnique = ObjectEffect.Techniques[m_nTechniqueID];
                }
                else
                {
                    ObjectEffect.CurrentTechnique = ObjectEffect.Techniques[(int)CRenderingManager.eObjectTechnique.HUD3D];
                }
                ObjectEffect.Parameters["g_Texture"].SetValue(s_RenderingManager.GetTexture(CRenderingManager.eTextureID.HUD_RETICLE));

                // Get the model from the rendering manager
                Model ObjectModel = s_RenderingManager.GetModel(CRenderingManager.eModelID.RETICLE);

                // Loop through each mesh
                foreach (ModelMesh Mesh in ObjectModel.Meshes)
                {
                    // Set the world matrix
                    ObjectEffect.Parameters["g_mWorld"].SetValue(m_mBoneTransforms[Mesh.ParentBone.Index]);

                    // Loop through each mesh part
                    foreach (ModelMeshPart MeshPart in Mesh.MeshParts)
                    {
                        // Set the GPU to read from both buffers (mesh part vertices and instance transforms)
                        Device.SetVertexBuffers(
                            new VertexBufferBinding(MeshPart.VertexBuffer, MeshPart.VertexOffset, 0),
                            new VertexBufferBinding(m_TransformVertexBuffer, 0, 1));

                        // Set the index buffer
                        Device.Indices = MeshPart.IndexBuffer;

                        // Loop through each pass
                        foreach (EffectPass Pass in ObjectEffect.CurrentTechnique.Passes)
                        {
                            // Begin the pass
                            Pass.Apply();

                            // Draw magic things
                            Device.DrawInstancedPrimitives(
                                PrimitiveType.TriangleList, 0, 0,
                                MeshPart.NumVertices, MeshPart.StartIndex,
                                MeshPart.PrimitiveCount, m_mRenderingTransforms.Length);
                        }
                    }
                }
                /* END: Render stuff =================================================================*/
                ////////////////////////////////////////////////////////////////////////////////////////

                // Draw connecting lines
                List<List<Vector3>> LineListList = new List<List<Vector3>>();
                List<int> LineIndexList = new List<int>();
                int nCurrentSatellite = -1;
                int nLineListIndex = -1;

                for(int iIndex = 0; iIndex < m_SatelliteIndexStack.Count; ++iIndex)
                {
                    if (m_SatelliteIndexStack.ElementAt(iIndex) != nCurrentSatellite)
                    {
                        LineListList.Add(new List<Vector3>());
                        ++nLineListIndex;
                        nCurrentSatellite = m_SatelliteIndexStack.ElementAt(iIndex);
                        LineIndexList.Add(nCurrentSatellite);
                    }

                    LineListList[nLineListIndex].Add(m_DestinationStack.ElementAt(iIndex));
                }

                HUDEffect.Parameters["g_Texture"].SetValue(s_RenderingManager.GetTexture(m_eGridTextureID));
                HUDEffect.Parameters["g_mWorld"].SetValue(Matrix.Identity);

                // Loop through each pass
                for(int iLineList = 0; iLineList < LineListList.Count; ++iLineList)
                {
                    List<Vector3> LineList = LineListList[iLineList];
                    VertexPositionTexture[] vVertices = new VertexPositionTexture[LineList.Count + 1];
                    vVertices[0] = new VertexPositionTexture(
                        s_ObjectManager.GetCelestialBody().GetPosition(LineIndexList[iLineList]),
                        new Vector2(0.5f, 0.5f));

                    for(int iVertex = 0; iVertex < LineList.Count; ++iVertex)
                    {
                        vVertices[iVertex + 1] = new VertexPositionTexture(LineList[iVertex], new Vector2(0.5f, 0.5f));
                    }

                    foreach (EffectPass Pass in HUDEffect.CurrentTechnique.Passes)
                    {
                        // Begin the pass
                        Pass.Apply();

                        // Draw magic things
                        Device.DrawUserPrimitives<VertexPositionTexture>(PrimitiveType.LineStrip, vVertices, 0, LineList.Count);
                    }
                }
            }
        }
    }
}
